package net.avcompris.binding.yaml.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.NamespaceContext;

import net.avcompris.binding.BindConfiguration;
import net.avcompris.binding.annotation.Namespaces;
import net.avcompris.binding.impl.AbstractBinderInvocationHandler;
import net.avcompris.binding.impl.BinderXPathVariableResolver;
import net.avcompris.binding.yaml.YamlBinder;
import net.avcompris.binding.yaml.YamlBindingException;
import net.avcompris.jaxen.yaml.YamlXPath;
import net.avcompris.logging.AvcLog;

import org.apache.commons.logging.LogFactory;
import org.jaxen.JaxenException;
import org.jaxen.XPath;

import com.avcompris.common.annotation.Nullable;
import com.avcompris.lang.NotImplementedException;
import com.avcompris.util.XPathUtils;

final class JaxenYamlBinderInvocationHandler extends
		AbstractBinderInvocationHandler<Object> {

	/**
	 * constructor.
	 */
	public JaxenYamlBinderInvocationHandler(final YamlBinder binder,
			final ClassLoader classLoader, final Class<?> clazz,
			final Object rootYaml, final BindConfiguration configuration) {

		super(binder, classLoader, clazz, rootYaml, Object.class, configuration);

		final Namespaces nsAnnotation = clazz.getAnnotation(Namespaces.class);

		if (nsAnnotation == null) {

			nsContext = null;

		} else {

			final String[] ns = nsAnnotation.value();

			nsContext = XPathUtils.calcNamespaceContext(ns);
		}
	}

	private final NamespaceContext nsContext;

	/**
	 * return the text content of a given node.
	 */
	@Override
	protected String getTextContent(final Object node) {

		nonNullArgument(node, "node");

		if (node instanceof YamlContent) {

			return ((YamlContent) node).getTextContent();
		}

		throw new NotImplementedException();
	}

	private static final AvcLog log = new AvcLog(
			LogFactory.getLog(JaxenYamlBinderInvocationHandler.class));

	@Override
	protected Object evaluateToNode(final String expression,
			final BinderXPathVariableResolver resolver, final Object node)
			throws YamlBindingException {

		nonNullArgument(expression, "xpathExpression");
		nonNullArgument(resolver, "resolver");
		nonNullArgument(node, "node");

		final Object value;

		try {

			final XPath xpath = new YamlXPath(getRootNode(), expression,
					nsContext, resolver, getConfiguration());

			if (log.isDebugEnabled()) {
				log.debugf("evaluateToNode(", expression, ", ", node, ")");
			}

			value = xpath.selectSingleNode(node);

		} catch (final JaxenException e) {

			throw new YamlBindingException(expression, e);
		}

		if (log.isDebugEnabled()) {
			log.debugf("  => evaluateToNode(): ", value);
		}

		return value;
	}

	@Override
	protected double evaluateToNumber(final String expression,
			final BinderXPathVariableResolver resolver, final Object node)
			throws YamlBindingException {

		nonNullArgument(expression, "xpathExpression");
		nonNullArgument(resolver, "resolver");
		nonNullArgument(node, "node");

		final Number value;

		try {

			final XPath xpath = new YamlXPath(getRootNode(), expression,
					nsContext, resolver, getConfiguration());

			if (log.isDebugEnabled()) {
				log.debugf("evaluateToNumber(", expression, ", ", node, ")");
			}

			value = xpath.numberValueOf(node);

		} catch (final JaxenException e) {

			throw new YamlBindingException(expression, e);
		}

		if (log.isDebugEnabled()) {
			log.debugf("  => evaluateToNumber(): ", value);
		}

		return value.doubleValue();
	}

	@Override
	protected String evaluateToString(final String expression,
			final BinderXPathVariableResolver resolver, final Object node)
			throws YamlBindingException {

		nonNullArgument(expression, "xpathExpression");
		nonNullArgument(resolver, "resolver");
		nonNullArgument(node, "node");

		final String value;

		try {

			final XPath xpath = new YamlXPath(getRootNode(), expression,
					nsContext, resolver, getConfiguration());

			if (log.isDebugEnabled()) {
				log.debugf("evaluateToString(", expression, ", ", node, ")");
			}

			value = xpath.stringValueOf(node);

		} catch (final JaxenException e) {

			throw new YamlBindingException(expression, e);
		}

		if (log.isDebugEnabled()) {
			log.debugf("  => evaluateToString(): ", value);
		}

		return value;
	}

	@Override
	protected boolean evaluateToBoolean(final String expression,
			final BinderXPathVariableResolver resolver, final Object node)
			throws YamlBindingException {

		nonNullArgument(expression, "xpathExpression");
		nonNullArgument(resolver, "resolver");
		nonNullArgument(node, "node");

		if (log.isDebugEnabled()) {
			log.debugf("evaluateToBoolean(", expression, ", ", node, ")");
		}

		final String text = evaluateToString(expression, resolver, node);

		final boolean value = Boolean.parseBoolean(text);

		if (log.isDebugEnabled()) {
			log.debugf("  => evaluateToBoolean(): ", value);
		}

		return value;
	}

	@Override
	protected List<Object> evaluateToList(final String expression,
			final BinderXPathVariableResolver resolver, final Object node)
			throws YamlBindingException {

		nonNullArgument(expression, "xpathExpression");
		nonNullArgument(resolver, "resolver");
		nonNullArgument(node, "node");

		final List<?> list;

		try {

			final XPath xpath = new YamlXPath(getRootNode(), expression,
					nsContext, resolver, getConfiguration());

			if (log.isDebugEnabled()) {
				log.debugf("evaluateToList(", expression, ", ", node, ")");
			}

			list = xpath.selectNodes(node);

		} catch (final JaxenException e) {

			throw new YamlBindingException(expression, e);
		}

		if (log.isDebugEnabled()) {
			log.debugf("  => evaluateToList(): ", list);
		}

		final List<Object> objectList = new ArrayList<Object>();

		for (final Object o : list) {

			objectList.add(o);
		}

		return objectList;
	}

	@Override
	public Map<String, Object> getAttributes(final Object node) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public Iterable<Object> getChildren(final Object node) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public void remove(final Object node) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public void removeAttribute(final Object node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public void setAttribute(final Object attributeNode,
			@Nullable final Object value) {

		nonNullArgument(attributeNode, "attributeNode");

		throw new NotImplementedException();
	}

	@Override
	public void setAttribute(final Object node, final String name,
			@Nullable final Object value) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public void setNode(final Object node, @Nullable final Object value) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public Object addToChildren(final Object node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}

	@Override
	public void setValue(final Object node, final Object value) {

		nonNullArgument(node, "node");
		nonNullArgument(value, "value");

		throw new NotImplementedException();
	}

	@Override
	public Object getParent(final Object node) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public String getName(final Object node) {

		nonNullArgument(node, "node");

		throw new NotImplementedException();
	}

	@Override
	public void setName(final Object node, final String name) {

		nonNullArgument(node, "node");
		nonNullArgument(name, "name");

		throw new NotImplementedException();
	}
}
